#pragma once
#ifndef LIT__STREAM__HH__
#define LIT__STREAM__HH__

#include <LitPrerequisites.h>
#include <fstream>
#include <sstream>
#include <tinyxml.h>

namespace Lit
{
	class LIT_API Stream
	{
	public:
		enum Access
		{
			ReadAccess=1<<1,
			WriteAccess=1<<2,
			BinaryAccess=1<<3,
			TextAccess=1<<4,
			TextRead=ReadAccess|TextAccess,
			TextWrite=WriteAccess|TextAccess,
			BinaryRead=ReadAccess|BinaryAccess,
			BinaryWrite=WriteAccess|BinaryAccess
		};
	protected:
		std::string mName;
		Access mAccess;
	public:
		Stream( Access access = TextRead );
		Stream( const std::string &name, Access access = TextRead );
		Stream( const Stream &rk );
		virtual ~Stream();

		const std::string &getName() const;
		Access getAccess() const;
		std::ios::open_mode getStdAccess() const;
		virtual size_t size() const = 0;
		virtual bool open( const std::string &name, Access access = TextRead ) = 0;
		virtual void close() = 0;
		virtual Stream& read( size_t num_chars, std::string & out ) = 0;
		virtual Stream& write( const std::string& str ) = 0;
		virtual Stream& readUntil( char c, std::string& out = std::string() ) = 0;
		virtual bool eof() const = 0;
		virtual bool valid() const = 0;
		std::string readLine();
		std::string readAll();
	};

	class LIT_API MemoryStream : public Stream
	{
		std::stringstream stream;
	public:
		MemoryStream( Access access = TextRead );
		MemoryStream( const std::string &content, Access access = TextRead );
		MemoryStream( const std::string &name, const std::string &content, Access access = TextRead );
		MemoryStream( const MemoryStream &rk );
		~MemoryStream();

		std::stringstream& handle();
		const std::stringstream& handle() const;

		size_t size() const;
		void close();
		bool open( const std::string &name = "", Access access = TextRead );
		MemoryStream& read( size_t num_chars, std::string & out );
		MemoryStream& write( const std::string& str );
		MemoryStream& readUntil( char c, std::string & out = std::string() );
		bool eof() const;
		bool valid() const;
	};
	
	class LIT_API FileStream : public Stream
	{
		std::fstream mStream;
		size_t		mSize;
	public:
		FileStream( );
		FileStream( const std::string &name, Access access = TextRead );
		FileStream( const FileStream &rk );
		~FileStream();
		
		std::fstream& handle();
		const std::fstream& handle() const;

		size_t size() const;
		void close();
		bool open( const std::string &name = "", Access access = TextRead );
		FileStream& read( size_t num_chars, std::string & out );
		FileStream& write( const std::string& str );
		FileStream& readUntil( char c, std::string & out = std::string() );
		bool eof() const;
		bool valid() const;
	};

	typedef RefPtr<Stream> StreamPtr;
	typedef RefPtr<MemoryStream> MemoryStreamPtr;
	typedef RefPtr<FileStream> FileStreamPtr;

	template<typename T>
	class Serialiser
	{
	protected:
		std::string mName;
		float mVersion;
	public:
		typename typedef T type;

		Serialiser( const std::string &name="", float version = 1.0f ) 
#ifndef NDEBUG
			: mName( String(name)(" v")(version)(" r")(PROJECT_VERSION_REVISION) )
#else 
			: mName( String(name)(" v")(version) )
#endif
			, mVersion(version){}
		virtual ~Serialiser(){}
		
		const float& getVersion() const{ return mVersion; }
		const std::string& getName() const{ return mName; }

		virtual StreamPtr& fromStream( StreamPtr& stream, T& out ) = 0;
		virtual StreamPtr& toStream( StreamPtr& stream, const T& in ) = 0;
	};

	template<typename T>
	class XmlSerialiser : public Serialiser<T>
	{
	public:
		XmlSerialiser( const std::string &name, float version = 1.0f ) : Serialiser(String((name.empty()?name:(name+" ")))("Xml Serialiser"), version){}
		~XmlSerialiser(){}
		
		virtual void saveObject( TiXmlDocument * doc, const T& in ) = 0;
		virtual void loadObject( TiXmlDocument * doc, const std::string &base_dir, T& out ) = 0;
		
		virtual StreamPtr& fromStream( StreamPtr& stream, T& out ){
			try
			{
				TiXmlDocument doc;
				std::string f(stream->readAll());
				std::string ret(doc.Parse( f.c_str() ));
				
				FilePath p(FilePath(stream->getName()).dirName()+"/");
				
				loadObject( &doc, p, out );
			}
			catch( std::exception & e )
			{
				Ogre::LogManager::getSingleton().logMessage("Cannot import object from stream '"+mName+"' because "+std::string(e.what()) );
				throw;
			}
			catch(...)
			{ throw; }
			return stream;
		}
		virtual StreamPtr& toStream( StreamPtr& stream, const T& in ){
			try
			{
				TiXmlDocument doc;
				doc.LinkEndChild( new TiXmlDeclaration( PROJECT_VERSION_STRING, "UTF-8", "yes" ) );
			
				saveObject( &doc, in );
			   
				TiXmlPrinter printer;
				doc.Accept(&printer);
				std::string str = printer.CStr();
			
				stream->write( str );
			}
			catch( std::exception & e )
			{
				Ogre::LogManager::getSingleton().logMessage("Cannot export object to stream '"+mName+"' because "+std::string(e.what()) );
				throw;
			}
			catch(...)
			{ throw; }
			return stream;
		}
	};
};

#endif